#include <str.h>
#include <draw.h>
#include <asm/type.h>
#include <macros.h>

static i8_t kernel_buf[KERNEL_BUFF_MAX_SIZE];

static inline i32_t skip_num(const i8_t **s) {
    int i = 0;
    while (is_digit(**s))
        i = i * 10 - '0' + *(*s)++;
    return i;
}

static inline i32_t number(char *str, i32_t pos, i32_t total, i64_t num, i32_t base, i32_t size, i32_t precision, i32_t type) {
    i8_t c, sign, tmp[64];
    const i8_t *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    i32_t i;

    if (type & SMALL)
        digits = "0123456789abcdefghijklmnopqrstuvwxyz";
    if (type & LEFT)
        type &= ~ZEROPAD;
    if (base < 2 || base > 36)
        return 0;
    c = (type & ZEROPAD) ? '0' : ' ';
    sign = 0;
    if (type & SIGN && num < 0) {
        sign = '-';
        num = -num;
    } else
        sign = (type & PLUS) ? '+' : ((type & SPACE) ? ' ' : 0);
    if (sign)
        size--;
    if (type & SPECIAL) {
        if (base == 16)
            size -= 2;
        else if (base == 8)
            size--;
    }
    i = 0;
    if (num == 0)
        tmp[i++] = '0';
    else
        while (num != 0)
            tmp[i++] = digits[do_div(num, base)];
    if (i > precision)
        precision = i;
    size -= precision;
    if (!(type & (ZEROPAD + LEFT)))
        while ((size-- > 0) && (pos < total))
            str[pos++] = ' ';
    if (sign && (pos < total))
        str[pos++] = sign;
    if (type & SPECIAL) {
        if ((base == 8) && (pos < total))
            str[pos++] = '0';
        else if (base == 16) {
            if (pos < total)
                str[pos++] = '0';
            if (pos < total)
                str[pos++] = digits[33];
        }
    }
    if (!(type & LEFT))
        while ((size-- > 0) && (pos < total))
            str[pos++] = c;

    while ((i < precision--) && (pos < total))
        str[pos++] = '0';
    while ((i-- > 0) && (pos < total))
        str[pos++] = tmp[i];
    while ((size-- > 0) && (pos < total))
        str[pos++] = ' ';
    return pos;
}

static inline i32_t writestring(i8_t *buf, i32_t len, const i8_t *fmt, va_list args) {
    i8_t *str, *s;
    i32_t flags;
    i32_t field_width;
    i32_t precision;
    i32_t ilen, i, j = 0;

    i32_t qualifier; /* 'h', 'l', 'L' or 'Z' for integer fields */

    for (str = buf; (j < len) && *fmt; fmt++) {
        if (*fmt != '%') {
            str[j++] = *fmt;
            continue;
        }
        flags = 0;
repeat:
        fmt++;
        switch (*fmt) {
        case '-':
            flags |= LEFT;
            goto repeat;
        case '+':
            flags |= PLUS;
            goto repeat;
        case ' ':
            flags |= SPACE;
            goto repeat;
        case '#':
            flags |= SPECIAL;
            goto repeat;
        case '0':
            flags |= ZEROPAD;
            goto repeat;
        }

        /* get field width */

        field_width = -1;
        if (is_digit(*fmt))
            field_width = skip_num(&fmt);
        else if (*fmt == '*') {
            fmt++;
            field_width = va_arg(args, i32_t);
            if (field_width < 0) {
                field_width = -field_width;
                flags |= LEFT;
            }
        }

        /* get the precision */

        precision = -1;
        if (*fmt == '.') {
            fmt++;
            if (is_digit(*fmt))
                precision = skip_num(&fmt);
            else if (*fmt == '*') {
                fmt++;
                precision = va_arg(args, i32_t);
            }
            if (precision < 0)
                precision = 0;
        }

        qualifier = -1;
        if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' || *fmt == 'Z') {
            qualifier = *fmt;
            fmt++;
        }

        switch (*fmt) {
        case 'c':

            if (!(flags & LEFT))
                while ((--field_width > 0) && (j < len))
                    str[j++] = ' ';
            if (j < len)
                str[j++] = (u8_t)va_arg(args, int);
            while ((--field_width > 0) && (j < len))
                str[j++] = ' ';
            break;

        case 's':

            s = va_arg(args, i8_t *);
            if (!s)
                s = '\0';
            ilen = strlen(s);
            if (precision < 0)
                precision = ilen;
            else if (ilen > precision)
                ilen = precision;

            if (!(flags & LEFT))
                while ((ilen < field_width--) && (j < len))
                    str[j++] = ' ';
            for (i = 0; (i < ilen) && (j < len); i++)
                str[j++] = *s++;
            while ((ilen < field_width--) && (j < len))
                str[j++] = ' ';
            break;

        case 'o':

            if (qualifier == 'l')
                j = number(str, j, len, va_arg(args, u64_t), 8, field_width, precision, flags);
            else
                j = number(str, j, len, va_arg(args, u32_t), 8, field_width, precision, flags);
            break;

        case 'p':

            if (field_width == -1) {
                field_width = 2 * sizeof(void *);
                flags |= ZEROPAD;
            }

            j = number(str, j, len, (u64_t)va_arg(args, void *), 16, field_width, precision, flags);
            break;

        case 'x':

            flags |= SMALL;

        case 'X':

            if (qualifier == 'l')
                j = number(str, j, len, va_arg(args, u64_t), 16, field_width, precision, flags);
            else
                j = number(str, j, len, va_arg(args, u32_t), 16, field_width, precision, flags);
            break;

        case 'd':
        case 'i':

            flags |= SIGN;
        case 'u':

            if (qualifier == 'l')
                j = number(str, j, len, va_arg(args, u64_t), 10, field_width, precision, flags);
            else
                j = number(str, j, len, va_arg(args, u32_t), 10, field_width, precision, flags);
            break;

        case 'n':

            if (qualifier == 'l') {
                i64_t *ip = va_arg(args, i64_t *);
                *ip = (str - buf);
            } else {
                int *ip = va_arg(args, i32_t *);
                *ip = (str - buf);
            }
            break;

        case '%':
            if (j < len)
                str[j++] = '%';
            break;

        default:
            if (j < len)
                str[j++] = '%';
            if ((j < len) && *fmt)
                str[j++] = *fmt;
            else
                fmt--;
            break;
        }
    }
    if (j == len)
        str[j - 1] = '\0';
    else

        str[j] = '\0';
    return j;
}


i32_t printk(const i8_t *fmt, ...) {
    i32_t n;
    va_list ap;

    va_start(ap, fmt);
    n = writestring(kernel_buf, sizeof(kernel_buf), fmt, ap);
    va_end(ap);

    shows(kernel_buf, n);
    return n;
}
